package online.heycm.lock;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

/**
 * 分布式锁切面，环绕通知上锁/解锁
 *
 * @author heycm
 * @since 2023/1/27 15:45
 */
@Aspect
// @Component 通过@EnableDistributedLock加载
public class DistributedLockAop {

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(online.heycm.lock.DistributedLock)")
    public void cut() {}

    @Around("cut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        final MethodSignature signature = (MethodSignature) pjp.getSignature();
        final DistributedLock lock = signature.getMethod().getAnnotation(DistributedLock.class);
        Object result = null;
        try {
            this.lock(lock, signature, pjp);
            result = pjp.proceed();
        } finally {
            this.unlock(lock);
        }
        return result;
    }

    private void lock(DistributedLock lock, MethodSignature signature, ProceedingJoinPoint pjp) throws IllegalAccessException {
        if (lock == null) {
            return;
        }
        String key = "";
        Object keyVal = null;
        boolean findKey = false;
        // 所有参数
        final Object[] args = pjp.getArgs();
        // 参数所有注解，第一层下标是参数下标，第二层下标是注解下标（一个方法多个参数，一个参数多个注解）
        Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();

        // 首先找参数上有没有 @DistributedLockKey 注解，如果有，认为该参数值为锁的key
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] parameterAnnotation = parameterAnnotations[i];
            for (Annotation annotation : parameterAnnotation) {
                if (annotation instanceof DistributedLockKey) {
                    keyVal = args[i];
                    findKey = true;
                    break;
                }
            }
            if (findKey) {
                break;
            }
        }
        // 其次找参数对象属性有没有 @DistributedLockKey 注解，如果有，认为该属性值为锁的key
        if (!findKey) {
            for (Object obj : args) {
                final Field[] fields = obj.getClass().getDeclaredFields();
                for (Field f : fields) {
                    DistributedLockKey distributedLockKey = f.getAnnotation(DistributedLockKey.class);
                    if (distributedLockKey != null) {
                        f.setAccessible(true);
                        keyVal = f.get(obj);
                        findKey = true;
                        break;
                    }
                }
                if (findKey) {
                    break;
                }
            }
        }
        // 找不到 @DistributedLockKey 注解
        if (!findKey) {
            throw new RuntimeException("DistributedLock get lock [key] failed.");
        }
        // 找到 @DistributedLockKey 注解，但其值为empty
        if (ObjectUtils.isEmpty(keyVal)) {
            throw new RuntimeException("DistributedLock get lock [key] is not be allowed empty.");
        }
        key = String.format("%s%s", lock.prefix(), keyVal);
        boolean ok = redisLock.lock(key, lock.waitTime(), lock.leaseTime());
        if (!ok) {
            throw new DistributedLockException(String.format("DistributedLock key [%s] get lock failed.", key));
        }
    }

    private void unlock(DistributedLock lock) {
        if (lock == null) {
            return;
        }
        redisLock.unlock();
    }
}
